/*
 * AbstractDispatchingLayerProxy.java   0.0.1    Nov 20, 2009
 * 
 * Copyright (c) 2009 mentalsmash.org
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * The use of the Apache License does not indicate that this project is
 * affiliated with the Apache Software Foundation.
 */
package org.mentalsmash.tazio.net.ril.impl;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.mentalsmash.documentation.Concurrency;
import org.mentalsmash.tazio.commons.identifiers.OID;
import org.mentalsmash.tazio.net.ril.DispatchingLayerProxy;
import org.mentalsmash.tazio.net.ril.RemoteInvocationMessageHandler;
import org.mentalsmash.tazio.net.ril.exceptions.FailedMessageDeliveryException;
import org.mentalsmash.tazio.net.ril.exceptions.NoResponseReceivedException;
import org.mentalsmash.tazio.net.ril.messages.MethodInvocationRequestMessage;
import org.mentalsmash.tazio.net.ril.messages.MethodInvocationResponseMessage;
import org.mentalsmash.tazio.net.ril.messages.RILMessage;
import org.mentalsmash.tazio.net.ril.messages.RILRequestMessage;
import org.mentalsmash.tazio.net.ril.messages.RILResponseMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.inject.Inject;

/**
 * @version 0.0.1 Nov 20, 2009
 * @author Andrea Sorbini <as@mentalsmash.org>
 * 
 */
@Concurrency(concurrentAccess = true, specification = "access to the expectingResponse map "
	+ "is synchronized, since it might happen concurrently from sending methods, invoked by"
	+ "other RIL's components, and receiving methods, invoked by the Dispatching Layer, and"
	+ "it needs to be consistent in order to guarantee correct coupling of request messages and"
	+ " their responses")
public abstract class AbstractDispatchingLayerProxy implements
	DispatchingLayerProxy {

    private static final Logger log = LoggerFactory
	    .getLogger(AbstractDispatchingLayerProxy.class);

    // private final RemoteInvoker invoker;

    private final RemoteInvocationMessageHandler handler;

    private long requestTimeout = 10000;
    private final Map<OID, RILRequestMessage> expectingResponse = new HashMap<OID, RILRequestMessage>();
    private final Set<RILResponseMessage> unknownResponses = new HashSet<RILResponseMessage>();

    @Inject
    public AbstractDispatchingLayerProxy(RemoteInvocationMessageHandler handler) {
	// this.invoker = invoker;

	this.handler = handler;
    }

    @Inject
    public AbstractDispatchingLayerProxy(
	    RemoteInvocationMessageHandler handler, long requestTimeout) {
	this(handler);
	this.requestTimeout = requestTimeout;
    }

    protected void expectingResponse(RILRequestMessage msg) {
	synchronized (expectingResponse) {
	    if (!expectingResponse.containsKey(msg)) {
		expectingResponse.put(msg.getOID(), msg);
	    }
	}
    }

    protected void responseReceived(RILResponseMessage msg) {
	synchronized (expectingResponse) {
	    if (expectingResponse.containsKey(msg.getOriginal())) {
		RILRequestMessage request = this.expectingResponse.get(msg
			.getOriginal());

		synchronized (request) {
		    request.setResponse(msg);
		    request.notifyAll();
		}

		this.expectingResponse.remove(request.getOID());
	    } else {
		unknownResponses.add(msg);
		log.warn("Response for unknown request received : " + msg);
	    }
	}
    }

    protected RILResponseMessage waitResponse(RILRequestMessage msg)
	    throws NoResponseReceivedException {
	RILResponseMessage response = null;

	synchronized (expectingResponse) {

	    for (RILResponseMessage resp : this.unknownResponses) {
		if (resp.getOriginal().equals(msg.getOID())) {
		    response = resp;
		    break;
		}
	    }

	    if (response != null) {
		this.unknownResponses.remove(response);
		return response;
	    }

	}

	synchronized (msg) {
	    while (msg.getResponse() == null) {
		try {
		    msg.wait(this.requestTimeout);
		} catch (InterruptedException e) {
		    throw new NoResponseReceivedException(msg);
		}
	    }

	    response = msg.getResponse();
	}
	
	return response;
    }

    @Override
    public RILResponseMessage sendMessage(RILRequestMessage msg)
	    throws FailedMessageDeliveryException, NoResponseReceivedException {

//	System.gc();
	
	this.expectingResponse(msg);

	try {
	    this.actuallySendMessage(msg);
	} catch (FailedMessageDeliveryException e) {
	    log.error("Failed to delivery message.");
	    throw new NoResponseReceivedException(msg);
	}

	RILResponseMessage response = this.waitResponse(msg);

	return response;

    }

    @Override
    public MethodInvocationResponseMessage sendMethodInvocationAtMostOnce(
	    MethodInvocationRequestMessage msg)
	    throws FailedMessageDeliveryException, NoResponseReceivedException {
	
//	System.gc();
	
	this.expectingResponse(msg);

	try {
	    this.actuallySendMethodInvocationAtMostOnce(msg);
	} catch (FailedMessageDeliveryException e) {
	    log.error("Failed to delivery message invocation at most once");
	    throw new NoResponseReceivedException(msg);
	}

	MethodInvocationResponseMessage response = (MethodInvocationResponseMessage) this
		.waitResponse(msg);

	return response;
    }

    @Override
    public MethodInvocationResponseMessage sendMethodInvocationToGroup(
	    MethodInvocationRequestMessage msg)
	    throws FailedMessageDeliveryException, NoResponseReceivedException {
	
//	System.gc();
	
	this.expectingResponse(msg);

//	try {
	    this.actuallySendMethodInvocationToGroup(msg);
//	} catch (FailedMessageDeliveryException e) {
//	    log.error("Failed to delivery message invocation to group");
//	    throw new NoResponseReceivedException(msg);
//	}

	MethodInvocationResponseMessage response = (MethodInvocationResponseMessage) this
		.waitResponse(msg);

	return response;
    }

    protected RILMessage handleIncomingMessage(RILMessage msg) {
	if (msg instanceof RILRequestMessage) {
	    // RemoteInvocationMessageHandler handler = this.invoker
	    // .getMessageHandler();
	    RILResponseMessage response = this.handler
		    .handleRequest((RILRequestMessage) msg);
	    return response;
	} else if (msg instanceof RILResponseMessage) {
	    this.responseReceived((RILResponseMessage) msg);
	    return null;
	} else {
	    log.warn("Unknown type of message received : " + msg + " (type: "
		    + msg.getClass().getCanonicalName() + ")");

	    return null;
	}

    }

    protected abstract void actuallySendMessage(RILRequestMessage msg)
	    throws FailedMessageDeliveryException;

    protected abstract void actuallySendMethodInvocationAtMostOnce(
	    MethodInvocationRequestMessage msg)
	    throws FailedMessageDeliveryException;

    protected abstract void actuallySendMethodInvocationToGroup(
	    MethodInvocationRequestMessage msg)
	    throws FailedMessageDeliveryException;

}
